class CorpusEmitter
  PIPE = "\n    | "

  class CSArg
    TRANSLATIONS = {"integer" => "number",
                    "string"  => "string",
                    "float"   => "number",
                    "boolean" => "boolean" }

    attr_reader :name, :allowed_values

    def initialize(name:, allowed_values:)
        @name, @allowed_values = name, allowed_values
    end

    def camelize
      name.camelize
    end

    def values
      allowed_values
        .map { |v| TRANSLATIONS[v] || v.camelize }
        .uniq
        .sort
        .join(PIPE)
    end

    def to_ts
      "\n    #{name}: #{values};"
    end
  end


  class CSNode
    UNDEFINED = "undefined"

    attr_reader :name, :allowed_args, :allowed_body_types

    def initialize(name:, allowed_args:, allowed_body_types: [])
        @name,
        @allowed_args,
        @allowed_body_types = name, allowed_args, allowed_body_types
    end

    def camelize
        name.camelize
    end

    def arg_names
      allowed_args
        .map{ |x| ARGS[x] }
        .each { |x| raise "NON-EXISTENT ARG TYPE" unless x }
        .map(&:to_ts)
        .join("")
    end

    def items_joined_by_pipe
      allowed_body_types.map(&:camelize).join(PIPE)
    end

    def body_names
      b = items_joined_by_pipe
      (b.length > 0) ? "(#{b})[] | undefined" : UNDEFINED
    end

    def has_body?
      body_names != UNDEFINED
    end

    def body_type
      "export type #{camelize}BodyItem = #{items_joined_by_pipe};" if has_body?
    end

    def body_attr
      "body?: #{ has_body? ? (camelize + "BodyItem[] | undefined") : UNDEFINED };"
    end

    def to_ts
      """
      #{body_type}

      export interface #{camelize} {
        kind: #{name.inspect};
        args: {#{arg_names}
        };
        comment?: string | undefined;
        #{body_attr}
      }
      """
    end
  end

  HASH  = JSON.load(open("http://localhost:3000/api/corpus")).deep_symbolize_keys
  ARGS  = {}
  HASH[:args]
    .map  { |x| CSArg.new(x) }
    .each { |x| ARGS[x.name] = x }
  NODES = HASH[:nodes].map { |x| CSNode.new(x) }

  def const(key, val)
    "\nexport const #{key} = #{val};"
  end

  def enum_type(key, val, inspect = true)
    "\nexport type #{key} = #{(inspect ? val.map(&:inspect) : val).join(PIPE)};"
  end

  def self.generate
    self.new.generate
  end

  def generate
    result = NODES.map(&:to_ts)
    result.unshift("""
       // THIS INTERFACE WAS AUTO GENERATED ON #{Date.today}
       // DO NOT EDIT THIS FILE.
       // IT WILL BE OVERWRITTEN ON EVERY CELERYSCRIPT UPGRADE.
    """)
    result.push(enum_type :CeleryNode, NODES.map(&:name).map(&:camelize), false)
    result.push(const(:LATEST_VERSION, Sequence::LATEST_VERSION))
    result.push(const :DIGITAL, CeleryScriptSettingsBag::DIGITAL)
    result.push(const :ANALOG, CeleryScriptSettingsBag::ANALOG)
    result.push(enum_type :ALLOWED_PIN_MODES,
                CeleryScriptSettingsBag::ALLOWED_PIN_MODES)
    result.push(enum_type :ALLOWED_MESSAGE_TYPES,
                CeleryScriptSettingsBag::ALLOWED_MESSAGE_TYPES)
    result.push(enum_type :ALLOWED_CHANNEL_NAMES,
                CeleryScriptSettingsBag::ALLOWED_CHANNEL_NAMES)
    result.push(enum_type :ALLOWED_DATA_TYPES,
                CeleryScriptSettingsBag::ALLOWED_DATA_TYPES)
    result.push(enum_type :ALLOWED_OPS,
                CeleryScriptSettingsBag::ALLOWED_OPS)
    result.push(enum_type :ALLOWED_PACKAGES,
                CeleryScriptSettingsBag::ALLOWED_PACKAGES)
    result.push(enum_type :ALLOWED_AXIS, CeleryScriptSettingsBag::ALLOWED_AXIS)
    result.push(enum_type :Color, Sequence::COLORS)
    result.push(enum_type :LegalArgString, HASH[:args].map{ |x| x[:name] }.sort.uniq)
    result.push(enum_type :LegalKindString, HASH[:nodes].map{ |x| x[:name] }.sort.uniq)
    result.push(enum_type :LegalSequenceKind, CeleryScriptSettingsBag::STEPS.sort)
    result.push(enum_type :DataChangeType, CeleryScriptSettingsBag::ALLOWED_CHAGES)
    result.push(enum_type :PointType, CeleryScriptSettingsBag::ALLOWED_POINTER_TYPE)
    result.push(enum_type :AllowedPinTypes, CeleryScriptSettingsBag::ALLOWED_PIN_TYPES)
    result.push(enum_type :PlantStage, CeleryScriptSettingsBag::PLANT_STAGES)

    File.open("latest_corpus.ts", "w") do |f|
      f.write(result.join.gsub("\n\n\n", "\n").gsub("\n\n", "\n").gsub("\n\n", "\n").strip)
    end
  end
end

CorpusEmitter.generate
